using System;
using System.Collections.Generic;
using System.Text;
using SideShow.Driver.ComInterop;
using System.Runtime.InteropServices;
using System.IO;
using SideShow.Events;

namespace SideShow.Driver
{
    public abstract class SideShowDevice<T> : IPnpCallbackHardware, ISideShowDevice where T : ISideShowDriver, new()
    {
        private IWDFDevice _device;
        private ISideShowDriver _driver;
        private ISideShowClassExtension _classExtension;

        protected SideShowDevice()
        {
            _driver = new T();
        }

        public void Initialize(IWDFDeviceInitialize pDeviceInit)
        {
            //
            // Set device properties.
            //
            pDeviceInit.SetLockingConstraint(_WDF_CALLBACK_CONSTRAINT.WdfDeviceLevel);

            //
            // Make sure we aren't the power policy owner
            //
            pDeviceInit.SetPowerPolicyOwnership(0);
        }

        #region IPnpCallbackHardware Members

        public void OnPrepareHardware(IWDFDevice pWdfDevice)
        {
            _device = pWdfDevice;
            if (_device == null || _driver == null)
            {
                Marshal.ThrowExceptionForHR(ComHResults.E_UNEXPECTED);
            }
            IWDFNamedPropertyStore pStore;
            _WDF_PROPERTY_STORE_DISPOSITION disposition;
            string name = null;
            
            ISideShowDriverInitialize driverInit = (ISideShowDriverInitialize)_driver;
            _device.RetrieveDevicePropertyStore(name, _WDF_PROPERTY_STORE_RETRIEVE_FLAGS.WdfPropertyStoreCreateIfMissing, out pStore, out disposition);
            this.Enabled = driverInit.Initialize(this, pStore);
            if (_classExtension == null)
            {
                _classExtension = new SideShowClassExtension();
            }
            IntPtr pUnkDevice = Marshal.GetIUnknownForObject(_device);
            IntPtr pUnkDriver = Marshal.GetIUnknownForObject(_driver);
            _classExtension.Initialize(pUnkDevice, pUnkDriver);
        }

        public void OnReleaseHardware(IWDFDevice pWdfDevice)
        {
            if (_driver != null)
            {
                ((ISideShowDriverInitialize) _driver).Deinitialize();
            }
            DriverShutdown();
            if (_classExtension != null)
            {
                _classExtension.Uninitialize(pWdfDevice);
            }
        }

        #endregion

        public void InitializeClassExtension()
        {
            if (_classExtension == null)
            {
                _classExtension = new SideShowClassExtension();
            }
        }


        public void ProcessIoControl(IWDFIoQueue pQueue, IWDFIoRequest pRequest, uint controlCode, uint inputBufferSizeInBytes, uint outputBufferSizeInBytes, out uint pcbWritten)
        {
            if (_classExtension == null)
            {
                Marshal.ThrowExceptionForHR(ComHResults.E_UNEXPECTED);
            }
            _classExtension.OnProcessIoControl(pQueue, pRequest, controlCode, inputBufferSizeInBytes, outputBufferSizeInBytes, out pcbWritten);
        }

        private void DriverInitialization()
        {
            
            
            
        }

        private void DriverShutdown()
        {

        }

        #region ISideShowDevice Members

        public void PostEvent(SideShowEvent e)
        {
            using (MemoryStream ms = new MemoryStream())
            {
                e.CreateEventData(ms);
                int size = (int) ms.Length;
                IntPtr p = Marshal.AllocCoTaskMem(size);
                ms.Position = 0;
                byte[] buf = new byte[size];
                ms.Read(buf, 0, size);
                Marshal.Copy(buf, 0, p, size);
                Guid id = e.Id;
                _device.PostEvent(ref id, _WDF_EVENT_TYPE.WdfEventBroadcast, p, (uint)size);    
            }

            
        }

        public bool Enabled
        {
            get 
            {
                return _device.GetPnpState(_WDF_PNP_STATE.WdfPnpStateDisabled) != _WDF_TRI_STATE.WdfTrue;
            }
            set 
            {
                _WDF_TRI_STATE v = value ? _WDF_TRI_STATE.WdfFalse : _WDF_TRI_STATE.WdfTrue;
                _device.SetPnpState(_WDF_PNP_STATE.WdfPnpStateDisabled, v);
                _device.SetPnpState(_WDF_PNP_STATE.WdfPnpStateDontDisplayInUI, v);
            }
        }

        #endregion
    }
}
